package vn.gq.tym.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashSet;
import java.util.LinkedHashMap;

import android.content.Context;
import android.util.Log;

public class CacheManager extends LinkedHashMap<Integer, Long> {

	private static final long serialVersionUID = 5262969259315601566L;
	public static final String TAG = CacheManager.class.getSimpleName();

	private static final int INITIAL_CAPACITY = 2;
	private static final int MAX_CAPACITY = 2*1024*1024;
    private static final float CLEAN_FACTOR = 0.75f;
    private static final float LOAD_FACTOR = 0.75f;
	public static CacheManager instance;
	
	private Context mContext;
	private NetworkOption networkOption = NetworkOption.OPTION_USE_CACHE;
	
    private int _size = 0;
    
    public CacheManager(Context context) {
        super(INITIAL_CAPACITY, LOAD_FACTOR, true);
        mContext = context;
        File f = new File(getCacheDir());
        if (!f.exists()) {
        	f.mkdirs();
        }
    }
    
    public String getCacheDir() {
    	return mContext.getCacheDir().toString();
    }
    
    public boolean isCacheMode() {
    	return networkOption == NetworkOption.OPTION_USE_CACHE;
    }
    
    @Override
    protected boolean removeEldestEntry(java.util.Map.Entry<Integer, Long> eldest) {
    	return false;
    }
    
    @Override
    public Long put(Integer key, Long value) {
		cleanUp();
		return super.put(key, value);
    }
    
    public void cleanUp() {
    	int cleanUpSize = (int)(MAX_CAPACITY * CLEAN_FACTOR);
    	if (_size > MAX_CAPACITY) {
			Log.i(TAG, String.format("----------------- clean up: %d, %d", _size, cleanUpSize));
    		HashSet<Integer> removeKeys = new HashSet<Integer>();
    		if (_size > cleanUpSize) {
    			int size = _size;
    			for (Integer key : keySet()) {
    				File f = new File(getCacheDir() + key);
    				if (f.exists()) {
    					removeKeys.add(key);
    					size -= f.length();
    					f.delete();
    					if (size < cleanUpSize) break;
    				}
        		}
    			if (removeKeys.size()>0) {
    				_size = size;
    				for (Integer key : removeKeys) {
    					super.remove(key);
    				}
    			}
    		}
    	}
    }
    
    @Override
    public Long remove(Object key) {
    	if (containsKey(key)) {
	    	File f = new File(getCacheDir() + key.toString());
	    	if (f.exists()) {
	    		_size -= f.length();
	    		f.delete();
	    	}
    	}
    	return super.remove(key);
    }
    
    @Override
    public int size() {
    	return _size;
    }

	public static CacheManager getInstance(Context context) {
		if (instance==null) {
			instance = new CacheManager(context);
			instance.load();
		}
		return instance;
	}
	
	public boolean isCached(URL uri) {
		File f = new File(getCacheDir() + String.valueOf(uri.hashCode()));
		return f.exists();
	}
	
	public InputStream getCache(URL uri) {
		int key = uri.hashCode();
		File f = new File(getCacheDir() + String.valueOf(key));
		if (!f.exists()) return null;
		try {
			// delete expired file
			if (!isCacheMode() && f.lastModified()<System.currentTimeMillis()) {
				Log.i(TAG, String.format("-------------------------- out of date %d", key));
				remove(key);
				return null;
			}
			return new FileInputStream(f);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public InputStream putCache(URL uri, InputStream is, final long expire) {
		if (is==null) return null;
		final int hash = uri.hashCode();
		try {
			int size = 0;
			int n;
			byte[] buffer = new byte[2048];
			final ByteArrayOutputStream fo = new ByteArrayOutputStream();
			while (0<(n = is.read(buffer))) {
				fo.write(buffer, 0, n);
				size += n;
			}
			fo.close();
			_size += size;
			put(hash, System.currentTimeMillis());
			Thread thread = new Thread(new Runnable() {
				@Override
				public void run() {
					File f = new File(getCacheDir() + String.valueOf(hash));
					try {
						FileOutputStream foo = new FileOutputStream(f);
						foo.write(fo.toByteArray());
						foo.close();
						f.setLastModified(expire);
					} catch (FileNotFoundException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			});
			thread.setPriority(Thread.MIN_PRIORITY);
			thread.start();

			return new ByteArrayInputStream(fo.toByteArray());
		} catch (IOException e) {
			e.printStackTrace();
			return is;
		}
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
	}
	
	public void save() {
		File f = new File(getCacheDir() + "cache.obj");
		FileOutputStream fo;
		try {
			fo = new FileOutputStream(f);
			DataOutputStream out = new DataOutputStream(fo);
			out.writeInt(super.size());
			for (Entry<Integer, Long> entry : entrySet()) {
				out.writeInt(entry.getKey());
				out.writeLong(entry.getValue());
			}
			out.close();
			fo.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void load() {
		_size = 0;
		File f = new File(getCacheDir() + "cache.obj");
		if (!f.exists()) return;
		FileInputStream fi;
		try {
			fi = new FileInputStream(f);
			DataInputStream in = new DataInputStream(fi);
			int size = in.readInt();
			for (int i=0; i<size; i++) {
				int key = in.readInt();
				long value = in.readLong();
				File f2 = new File(getCacheDir() + key);
				if (f2.exists()) {
					put(key, value);
					_size += f2.length();
				}
			}
			in.close();
			fi.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}